package vsvm.math.statistics;

import java.util.ArrayList;
import java.util.Iterator;

import vsvm.classifier.AbstractTestSet;
import vsvm.classifier.TestCase;
import vsvm.math.CoreMath;
import vsvm.math.Point;

public class ROCAnalysis {

	private int steps = 1000;
	private ArrayList <Point> ROCCurve;
	private AbstractTestSet testSet = null;	
	private Point classPoint = null;
	private double w  = 0; //W statistic
	private double se = 0; //Standard error
	
	public ROCAnalysis(AbstractTestSet t) {
		this.testSet = t;
		prepareROCCurve();
		prepareWSE();
	}
	
	public void setSteps(int steps) {
		this.steps = steps;
	}
	
	private void prepareROCCurve(){
		
		this.ROCCurve = new ArrayList();
		
		double step = 1.0/steps;
		
		/* if   p[positive] >= threshold -> positive
		 * else -> negative
		 */
		double threshold = 0;
		int tp, fp, tn, fn;
		
		
		double negative = 0, positive = 1;
		
		/* Find negative and positive Classes */		
		negative = this.testSet.getNegativeClass();
		positive = this.testSet.getPositiveClass();
		
		/* DEBUG */
		//System.out.println("N: " + negative + " P: " + positive);		
			
		while (threshold < 1) {
			int [] pn = this.getPositivesNegativesCount(threshold, positive, negative);
			this.ROCCurve.add(new Point(1 - this.getSpecificity(pn[2], pn[3]), this.getSensitivity(pn[0], pn[1])));			
			
			threshold += step; 		
		}
		int [] pn = this.getPositivesNegativesCount(1, positive, negative);
		this.ROCCurve.add(new Point(1 - this.getSpecificity(pn[2], pn[3]), this.getSensitivity(pn[0], pn[1])));		
		
		pn = this.getPositivesNegativesCount(0.5, positive, negative);		
		this.classPoint = new Point(1 - this.getSpecificity(pn[2], pn[3]), this.getSensitivity(pn[0], pn[1]));	
		
	}
	
	private void prepareWSE() {
		double negative = this.testSet.getNegativeClass();
		double positive = this.testSet.getPositiveClass();
		int [] pn = this.getPositivesNegativesCount(0.5, positive, negative);
		
		double[][] rows = new double[7][2];
		rows[0][0] = pn[0]; rows[0][1] = pn[1];
		rows[2][0] = pn[3]; rows[2][1] = pn[2];
		double np = rows[0][0] + rows[0][1];
		double nn = rows[2][0] + rows[2][1];
		
		rows[1][0] = nn - rows[2][0]; rows[1][1] = 0;
		rows[3][0] = 0; rows[3][1] = rows[0][0];
		
		for (int i = 0; i < 2; i++)
			rows[4][i] = rows[0][i]*rows[1][i] + 0.5*rows[0][i]*rows[2][i];
		
		this.w = (rows[4][0] + rows[4][1])/(np*nn);
		
		for (int i = 0; i < 2; i++)
			rows[5][i] = rows[2][i]*(rows[3][i]*rows[3][i] + rows[3][i]*rows[0][i] + rows[0][i]*rows[0][i]/3);
		
		for (int i = 0; i < 2; i++)
			rows[6][i] = rows[0][i]*(rows[1][i]*rows[1][i] + rows[1][i]*rows[2][i] + rows[2][i]*rows[2][i]/3);
		
		double q2 = ((rows[5][0] + rows[5][1])/(np*nn))/np;
		double q1 = ((rows[6][0] + rows[6][1])/(np*nn))/nn;
		
		
		se = Math.sqrt((w*(w - 1) + (nn - 1)*(q1 - w*w) + (np - 1)*(q2 - w*w))/(np*nn));		
	}

	private int[] getPositivesNegativesCount(double threshold, double positive, double negative){
	
		int tp, fp, tn, fn;
		tp = fp = tn = fn = 0;
		Iterator <TestCase> it = this.testSet.getTestCases();
		
		while (it.hasNext()) {
			TestCase tc = it.next();
			double predict = tc.getProbabilities()[1] >= threshold ? positive : negative;
			if (Math.abs(tc.getActualClass() - positive) < 1e-8) {
				if (Math.abs(tc.getActualClass() - predict) < 1e-8)
					tp++;
				else
					fn++;					
			} else {
				if (Math.abs(tc.getActualClass() - predict) < 1e-8)
					tn++;
				else
					fp++;
			}				
		}
		
		return new int[] {tp, fn, tn, fp};
	}
	
	public double getSensitivity(int truePositives, int falseNegatives) {
		if (falseNegatives == 0) return 1;
		return (1.0*truePositives)/(truePositives + falseNegatives);
	}
	
	public double getSpecificity(int trueNegatives, int falsePositives) {
		if (falsePositives == 0) return 1;
		return (1.0*trueNegatives)/(trueNegatives + falsePositives);
	}
	
	public Iterator <Point> getROCCurve() {
		return this.ROCCurve.iterator();
	}
	
	public Point getClassifierPoint() {
		return this.classPoint;
	}
	
	public double getAUC() {
		return CoreMath.getArea(this.ROCCurve);
	}
	
	public double getWStatistic() {
		return w;
	}
	
	public double getStandardError() {
		return se;
	}
	
	
}
